#include <stdio.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "esp_wifi.h"
#include "esp_mac.h"

#include <fcntl.h>
#include "esp_system.h"
#include "esp_console.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "esp_vfs_cdcacm.h"


#include "m_console.h"
#include "iperf.h"
#include "iperf_cmd.h"
#include "ping_cmd.h"


static const char *TAG = "Console";

const char* prompt = LOG_COLOR_I CONFIG_IDF_TARGET "> " LOG_RESET_COLOR;


uint32_t m_network_info_display(const char *if_key, const char *name)
{
    char *if_desc = NULL;
    char *net_status = NULL;
    char *net_def = NULL;
    uint8_t mac[6];
    if((NULL == if_key) || (NULL == name))
    {
        ESP_LOGI(TAG, "if_key is null");
        return 1;
    }
    esp_netif_t *esp_netif = esp_netif_get_handle_from_ifkey(if_key);
    
    if(NULL == esp_netif)
    {
        return 2;
    }
    esp_netif_t *def_netif = esp_netif_get_default_netif();
    esp_netif_get_mac(esp_netif, mac);
    if_desc = (char *)esp_netif_get_desc(esp_netif);
    
    if(false == esp_netif_is_netif_up(esp_netif))
    {
        net_status = "DOWN";
    }
    else
    {
        net_status = "UP";
    }
    
    if((esp_netif == def_netif) && (NULL != def_netif))
    {
        net_def = "DEF";
    }
    else
    {
        net_def = "NON";
    }

    esp_netif_ip_info_t ip_info;

    if(ESP_OK != esp_netif_get_ip_info(esp_netif, (esp_netif_ip_info_t *)&ip_info))
    {
        ESP_LOGI(TAG, "get ip info failed");
        return 3;
    }
    int netif_prio = esp_netif_get_route_prio(esp_netif);

    uint8_t *ip = NULL;

    printf("\r\n%-8s%s %d %s(%s)\r\n", if_desc, name, netif_prio, net_status, net_def);
    printf("%-8sHWaddr %02X:%02X:%02X:%02X:%02X:%02X\r\n", " ", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    ip = (uint8_t *)&ip_info.ip.addr;
    printf("%-8sIP %d.%d.%d.%d\r\n", " ", ip[0], ip[1], ip[2], ip[3]);
    ip = (uint8_t *)&ip_info.gw.addr;
    printf("%-8sGateway %d.%d.%d.%d\r\n", " ", ip[0], ip[1], ip[2], ip[3]);
    ip = (uint8_t *)&ip_info.netmask.addr;
    printf("%-8sMask %d.%d.%d.%d\r\n", " ", ip[0], ip[1], ip[2], ip[3]);
    return 0;
}


/**
 * @description: 
 * @param {int} argc
 * @param {char} *
 * @return {*}
 */
static int free_mem_callback(int argc, char **argv)
{
    // printf("%ld\n", esp_get_free_heap_size());
    printf("default free %d\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
    printf("spiram totel %d\n", heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
    printf("spiram free %d\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
    return 0;
}


/**
 * @description: 
 * @param {*}
 * @return {*}
 */
static void register_free(void)
{
    const esp_console_cmd_t cmd = {
        .command = "free",
        .help = "Get the current size of free heap memory",
        .hint = NULL,
        .func = &free_mem_callback,
    };
    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}


static int heap_size_callback(int argc, char **argv)
{
    uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
    printf("min heap size: %ld\n", heap_size);
    return 0;
}


/**
 * @description: 
 * @param {*}
 * @return {*}
 */
static void register_heap(void)
{
    const esp_console_cmd_t heap_cmd = {
        .command = "heap",
        .help = "Get minimum size of free heap memory that was available during program execution",
        .hint = NULL,
        .func = &heap_size_callback,
    };
    ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );

}


/**
 * @description: 
 * @param {int} argc
 * @param {char} *
 * @return {*}
 */
static int task_list_callback(int argc, char **argv)
{
    char InfoBuffer[1024] = {0}; 
    vTaskList((char *) &InfoBuffer);
    printf("task_name   status   priority   stack   num   core\r\n");
    printf("\r\n%s\r\n", InfoBuffer);

    return 0;
}


/**
 * @description:
 * @param {*}
 * @return {*}
 */
static void register_tasks(void)
{
    const esp_console_cmd_t cmd = {
        .command = "tasks",
        .help = "show task status list.",
        .hint = NULL,
        .func = &task_list_callback,
    };
    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}


typedef struct {
    struct arg_lit *type;
    struct arg_end *end;
} top_args_t;
static top_args_t top_args;

static int top_callback(int argc, char **argv)
{
    TaskStatus_t * pxStartTaskStatusArray;
    TaskStatus_t * pxEndTaskStatusArray;
    configRUN_TIME_COUNTER_TYPE ulTotalTime, ulStartTotalTime, ulEndTotalTime, ulStatsAsPercentage, ulExpTime;
    UBaseType_t uxStartArraySize;
    UBaseType_t uxEndArraySize;

    char core_id = 'X';

    int nerrors = arg_parse(argc, argv, (void **) &top_args);
    if (nerrors != 0) {
        arg_print_errors(stderr, top_args.end, argv[0]);
        return 0;
    }
    
    uxStartArraySize = uxTaskGetNumberOfTasks();
    if(0 >= uxStartArraySize)
    {
        return 0;
    }

    pxStartTaskStatusArray = malloc( uxStartArraySize * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */
    if(NULL != pxStartTaskStatusArray)
    {
        uxStartArraySize = uxTaskGetSystemState( pxStartTaskStatusArray, uxStartArraySize, &ulStartTotalTime );
    }

    if(0 == top_args.type->count)
    {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }

    printf("-----------------------------------------------------------\r\n");
    printf("%-16s %8s %16s %7s\r\n", "Task", "Core", "Run", "CPU");

    if(0 != top_args.type->count)
    {
        ulTotalTime = ulStartTotalTime/100;
        for(UBaseType_t j=0; j<uxStartArraySize; j++)
        {
            ulExpTime = pxStartTaskStatusArray[j].ulRunTimeCounter;
            ulStatsAsPercentage = ulExpTime/ulTotalTime;

            if(0 == pxStartTaskStatusArray[j].xCoreID)
            {
                core_id = '0';
            }
            else if(1 == pxStartTaskStatusArray[j].xCoreID)
            {
                core_id = '1';
            }
            else
            {
                core_id = 'X';
            }

            if((0==ulStatsAsPercentage) && (0 != ulExpTime))
            {
                printf("%-16s %7c %16lld %8s\r\n", pxStartTaskStatusArray[j].pcTaskName, core_id, ulExpTime, "<1%");
            }
            else
            {
                printf("%-16s %7c %16lld %7lld%%\r\n", pxStartTaskStatusArray[j].pcTaskName, core_id, ulExpTime, ulStatsAsPercentage);
            }
        }

        free( pxStartTaskStatusArray );
        return 0;
    }

    uxEndArraySize = uxTaskGetNumberOfTasks();
    pxEndTaskStatusArray = malloc( uxEndArraySize * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */
    if(NULL != pxEndTaskStatusArray)
    {
        uxEndArraySize = uxTaskGetSystemState( pxEndTaskStatusArray, uxEndArraySize, &ulEndTotalTime );
    }
    else
    {
        free( pxStartTaskStatusArray );
        return 0;
    }

    if(0 >= uxEndArraySize)
    {
        free( pxStartTaskStatusArray );
        return 0;
    }

    
    ulTotalTime = (ulEndTotalTime-ulStartTotalTime)/100;
    // printf("%-16s %10s %8s\r\n", "Task", "Run", "Occupy");
    
    for(UBaseType_t i=0; i<uxEndArraySize; i++)
    {
        for(UBaseType_t j=0; j<uxStartArraySize; j++)
        {
            if(pxEndTaskStatusArray[i].xHandle == pxStartTaskStatusArray[j].xHandle)
            {
                ulExpTime = pxEndTaskStatusArray[i].ulRunTimeCounter-pxStartTaskStatusArray[j].ulRunTimeCounter;
                ulStatsAsPercentage = ulExpTime/ulTotalTime;

                if(0 == pxEndTaskStatusArray[i].xCoreID)
                {
                    core_id = '0';
                }
                else if(1 == pxEndTaskStatusArray[i].xCoreID)
                {
                    core_id = '1';
                }
                else
                {
                    core_id = 'X';
                }

                if((0==ulStatsAsPercentage) && (0 != ulExpTime))
                {
                    printf("%-16s %7c %16lld %8s\r\n", pxEndTaskStatusArray[i].pcTaskName, core_id, ulExpTime, "<1%");
                }
                else
                {
                    printf("%-16s %7c %15lld %7lld%%\r\n", pxEndTaskStatusArray[i].pcTaskName, core_id, ulExpTime, ulStatsAsPercentage);
                }
                break;
            }
        }
    }

    free( pxStartTaskStatusArray );
    free( pxEndTaskStatusArray );

    return 0;
}


/**
 * @description: 
 * @param {*}
 * @return {*}
 */
static void register_top(void)
{
    top_args.type = arg_lit0("a", "all", "All occupied after power-on");
    top_args.end = arg_end(1);

    const esp_console_cmd_t cmd = {
        .command = "top",
        .help = "Display CPU occupancy rate",
        .hint = NULL,
        .func = &top_callback,
        .argtable = &top_args,
    };
    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}

/**
 * @description: 
 * @param {int} argc
 * @param {char} *
 * @return {*}
 */
static int reboot_callback(int argc, char **argv)
{
    esp_restart();
    return 0;
}


/**
 * @description: 
 * @param {*}
 * @return {*}
 */
static void register_reboot(void)
{
    const esp_console_cmd_t cmd = {
        .command = "reboot",
        .help = "Reboot the system",
        .hint = NULL,
        .func = &reboot_callback,
    };
    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}


/**
 * @description: 
 * @param {int} argc
 * @param {char} *
 * @return {*}
 */
static int ipconfig_callback(int argc, char **argv)
{
    m_network_info_display("WIFI_STA_DEF", "Wifi");
    m_network_info_display("WIFI_AP_DEF", "Wifi");
    m_network_info_display("PPP_DEF", "4G");
    return 0;
}



/**
 * @description: 
 * @param {*}
 * @return {*}
 */
static void register_ipconfig(void)
{
    const esp_console_cmd_t cmd = {
        .command = "ipconfig",
        .help = "Display network information",
        .hint = NULL,
        .func = &ipconfig_callback,
    };
    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}


typedef struct {
    struct arg_str *level;
    struct arg_str *module;
    struct arg_end *end;
} log_args_t;
static log_args_t log_args;


static int log_module_call_back(int argc, char **argv)
{
    int nerrors = arg_parse(argc, argv, (void **) &log_args);
    if (nerrors != 0) {
        arg_print_errors(stderr, log_args.end, argv[0]);
        return 0;
    }
    
    esp_log_level_t log_level = ESP_LOG_INFO;
    char *p_module = "*";
    char *p_level = "*";
    if(0 != log_args.module->count)
    {
        p_module = (char *)log_args.module->sval[0];
    }

    if(0 != log_args.level->count)
    {
        p_level = (char *)log_args.level->sval[0];
        if(0 == strcmp(p_level, "err"))
        {
            log_level = ESP_LOG_ERROR;
        }
        else if(0 == strcmp(p_level, "warn"))
        {
            log_level = ESP_LOG_WARN;
        }
        else if(0 == strcmp(p_level, "info"))
        {
            log_level = ESP_LOG_INFO;
        }
        else if(0 == strcmp(p_level, "debug"))
        {
            log_level = ESP_LOG_DEBUG;
        }
        else if(0 == strcmp(p_level, "none"))
        {
            log_level = ESP_LOG_NONE;
        }
        else
        {
            printf("\r\n[log] log level error...\r\n");
            return 0;
        }
    }

    esp_log_level_set(p_module, log_level);  // 打开所有日志

    return 0;
}
void register_log(void)
{
    log_args.level = arg_str0("l", "level", "<level>", "none,err,warn,info,debug");
    log_args.module = arg_str0("m", "module", "<module>", "log module");
    log_args.end = arg_end(1);

    const esp_console_cmd_t date_cmd = {
        .command = "log",
        .help = "esp log control",
        .hint = NULL,
        .func = &log_module_call_back,
        .argtable = &log_args
    };
    esp_console_cmd_register(&date_cmd);
}


static void console_cmd_register(void)
{
    esp_console_register_help_command();

    register_free();
    register_heap();
    register_tasks();
    register_top();
    register_reboot();
    register_ipconfig();

    register_log();

    /* From iperf-cmd */
    app_register_iperf_commands();
    // app_register_iperf_hook_func(iperf_hook_show_wifi_stats);
    /* From ping-cmd */
    ping_cmd_register_ping();
}




void uart_console_init(int channel, int baud_rate, int tx_gpio_num, int rx_gpio_num)
{
    esp_console_repl_t *repl = NULL;
    esp_console_repl_config_t repl_config =      \
    {                                                          
        .max_history_len = 8,            
        .history_save_path = NULL,        
        .task_stack_size = 1024 * 4,          
        .task_priority = 2,               
        .prompt = "IOT>",      
    };

    // esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
    esp_console_dev_uart_config_t uart_config = {
        .channel = channel,
        .baud_rate = baud_rate,
        .tx_gpio_num = tx_gpio_num,
        .rx_gpio_num = rx_gpio_num,
    };
    if(SOC_UART_HP_NUM <= uart_config.channel)
    {
        uart_config.channel = SOC_UART_HP_NUM - 1;
    }
    ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
    console_cmd_register();

    ESP_ERROR_CHECK(esp_console_start_repl(repl));
}

uint32_t m_console_init(m_console_conf_t *p_conf)
{
    if(NULL == p_conf)
    {
        return 1;
    }
    uart_console_init(p_conf->uart_num, p_conf->baudrate, p_conf->uart_tx, p_conf->uart_rx);

    return 0;
}


